import {
  onUnmounted,
  unref,
  ref,
  reactive,
  toRaw,
  watchEffect,
  getCurrentInstance,
  nextTick,
  computed,
} from 'vue';
import {
  VxeModalEventProps,
  VxeModalMethods,
  VxeModalProps,
  VxeModalInstance,
} from 'vxe-pc-ui/types/components/Modal';
import { error } from '@/utils/log';
import { isProdMode } from '@/utils/env';
import { tryOnUnmounted } from '@vueuse/core';
import { isFunction } from '@/utils/is';
import { isEqual } from 'lodash-es';

type PropValue<T> = T extends keyof ModalPropsVxe ? ModalPropsVxe[T] : never;

export type ModalPropsVxe = VxeModalProps &
  VxeModalEventProps & {
    [key: string]: any;
  };

// useModal Methods
export interface ModalMethods {
  setModalProps(props: ModalPropsVxe): void;
  setModalPropField<T extends keyof ModalPropsVxe>(prop: T, value: PropValue<T>): void;
  setModalLoading(flag?: boolean): boolean;
  getModalLoading(): boolean;
  getModalPropField<T extends keyof ModalPropsVxe>(prop: T): PropValue<T>;
  getModalProps(): ModalPropsVxe;
  openModal<T = any>(data?: T, openOnSet?: boolean): void;
  closeModal(): void;
  emitOpen(open: boolean, uid: number): void;
}
// useModalInstance Methods
export interface ModalMethodsInstance
  extends Omit<ModalMethods, 'openModal' | 'emitOpen'>,
    VxeModalMethods {
  getOpen: ComputedRef<boolean>;
}

const dataTransfer = reactive<any>({});
const openData = reactive<{ [key: number]: boolean }>({});

export function useVxeModal(): [(methods: ModalMethods, uuid: number) => void, ModalMethods] {
  const ModalRef = ref<ModalMethods>();
  const uid = ref<number>(0);
  const loaded = ref<Nullable<boolean>>(false);

  function register(modalMethod: ModalMethods, uuid: number) {
    // console.log(modalMethod, uuid);
    if (!getCurrentInstance()) {
      error('useVxeModal() can only be used inside setup() or functional components!');
    }
    uid.value = uuid;
    onUnmounted(() => {
      ModalRef.value = undefined;
      loaded.value = false;
      dataTransfer[String(unref(uid))] = null;
    });
    if (unref(loaded) && modalMethod === unref(ModalRef)) return;
    ModalRef.value = modalMethod;
    loaded.value = true;
    modalMethod.emitOpen = (open: boolean, uid: number) => {
      openData[uid] = open;
    };
  }
  const getInstance = () => {
    const instance = unref(ModalRef);
    // console.log(instance, ModalRef);
    if (!instance) {
      error('useVxeModal instance is undefined!');
    }
    return instance;
  };

  const methods: Partial<ModalMethods> = {
    setModalPropField<T extends keyof ModalPropsVxe>(prop: T, value: PropValue<T>): void {
      return getInstance()?.setModalPropField(prop, value);
    },

    setModalProps(props: ModalPropsVxe) {
      return getInstance()?.setModalProps(props);
    },

    setModalLoading(flag) {
      const loadingProp = 'loading';
      const state: boolean =
        flag !== undefined ? flag : !getInstance()?.getModalPropField(loadingProp);
      getInstance()?.setModalPropField(loadingProp, state);
      return state;
    },

    getModalLoading(): boolean {
      return getInstance()?.getModalPropField('loading') as boolean;
    },
    getModalPropField<T extends keyof ModalPropsVxe>(prop: T): PropValue<T> {
      return getInstance()?.getModalPropField(prop) as PropValue<T>;
    },

    getModalProps(): ModalPropsVxe {
      return getInstance()?.getModalProps() as ModalPropsVxe;
    },

    openModal: <T = any>(data?: T, openOnSet = true): void => {
      getInstance()?.setModalProps({
        modelValue: true,
      });
      if (!data) return;
      const id = unref(uid);
      if (openOnSet) {
        dataTransfer[id] = null;
        dataTransfer[id] = toRaw(data);
        return;
      }
      const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data));
      if (!equal) {
        dataTransfer[id] = toRaw(data);
      }
    },

    closeModal: () => {
      getInstance()?.setModalProps({ modelValue: false });
    },
  };

  return [register, methods as ModalMethods];
}

export const useVxeModalInner = (
  callbackFn?: Fn,
): [
  (modalInstance: ModalMethodsInstance & VxeModalInstance, uuid: number) => void,
  ModalMethodsInstance,
] => {
  // start?.stopPropagation();
  const modalInstanceRef = ref<Nullable<ModalMethodsInstance>>(null);
  const currentInstance = getCurrentInstance();
  const uidRef = ref<number>(0);

  const getInstance = () => {
    const instance = unref(modalInstanceRef);
    if (!instance) {
      error('useModalInner instance is undefined!');
    }
    return instance;
  };

  const register = (modalInstance: ModalMethodsInstance, uuid: number) => {
    // console.log('useModalInner', modalInstance, uuid);
    isProdMode() &&
      tryOnUnmounted(() => {
        modalInstanceRef.value = null;
      });
    uidRef.value = uuid;
    modalInstanceRef.value = modalInstance;
    currentInstance?.emit('register', modalInstance, uuid);
  };

  watchEffect(() => {
    const data = dataTransfer[unref(uidRef)];
    if (!data) return;
    if (!callbackFn || !isFunction(callbackFn)) return;
    nextTick(() => {
      callbackFn(data);
    });
  });

  const methods = setMethods(
    {
      setModalPropField<T extends keyof ModalPropsVxe>(prop: T, value: PropValue<T>): void {
        return getInstance()?.setModalPropField(prop, value);
      },

      setModalProps(props: ModalPropsVxe) {
        return getInstance()?.setModalProps(props);
      },

      setModalLoading(flag) {
        const loadingProp = 'loading';
        const state: boolean =
          flag !== undefined ? flag : !getInstance()?.getModalPropField(loadingProp);
        getInstance()?.setModalPropField(loadingProp, state);
        return state;
      },

      getModalLoading() {
        return getInstance()?.getModalPropField('loading') as boolean;
      },
      getModalPropField<T extends keyof ModalPropsVxe>(prop: T): PropValue<T> {
        return getInstance()?.getModalPropField(prop) as PropValue<T>;
      },

      getModalProps(): ModalPropsVxe {
        return getInstance()?.getModalProps() as ModalPropsVxe;
      },

      closeModal: () => {
        getInstance()?.setModalProps({ modelValue: false });
      },

      getOpen: computed((): boolean => {
        return openData[~~unref(uidRef)];
      }),
    },
    [
      'dispatchEvent',
      'open',
      'close',
      'getBox',
      'getPosition',
      'setPosition',
      'isMinimized',
      'isMaximized',
      'zoom',
      'minimize',
      'maximize',
      'revert',
    ],
  );

  function setMethods(
    methods: Partial<ModalMethodsInstance>,
    extendsFns: string[],
  ): ModalMethodsInstance {
    // 重写的方法排除 继承formMethods原有成员方法
    extendsFns.map((fnKey) => {
      if (!methods[fnKey]) {
        methods[fnKey] = function (...args) {
          console.log(getInstance?.()?.[fnKey]);
          return getInstance()?.[fnKey](...args);
        };
      }
    });

    return methods as ModalMethodsInstance;
  }
  return [register, methods];
};
